﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using Revolio;

namespace RevolioReader
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private Book book;

        private async void DoOpenBook(int id)
        {
            try
            {
                book = await Task<Book>.Factory.StartNew(
                    () => Book.Load(id)
                    );
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, "Cannot load book: " + ex.Message);
                return;
            }
            if (book.Availability != BookAvailability.Available)
            {
                MessageBox.Show(this, "This book is not available: " + book.Availability);
                return;
            }

            tvContents.Nodes.Clear();
            //TreeNode root = tvContents.Nodes.Add(book.Title + " - " + book.Issue);

            // Add a "Cover" node if the table of contents is empty or the
            // first entry does not point to page 0.
            if (book.Sections.Count == 0 || book.Sections[0].PageNumber != 0)
            {
                tvContents.Nodes.Add("Cover").Tag = 0;
            }

            // Add a node for each of the defined sections in the book.
            foreach (Section sec in book.Sections)
            {
                tvContents.Nodes.Add(sec.Title).Tag = sec.PageNumber;
            }

            // Select the "Cover" node to display the first page.
            tvContents.SelectedNode = tvContents.Nodes[0];

            // Set the window title to the name of the book.
            this.Text = string.Format("{0} - {1} - Revolio Reader",
                book.Title, book.Issue);
#if false
            // Download these pages.
            string dir = @"..\..\data";
            for (int p = 1; p <= book.Pages.Length; p++)
            {
                // Skip a page if the file is already in the directory.
                string filename = Path.Combine(dir, "large-" + p + ".jpg");
                if (File.Exists(filename))
                    continue;

                // Download this page. Change to high resolution if needed.
                string url = book.Pages[p - 1];
                if (url.EndsWith("-1.jpg", StringComparison.InvariantCultureIgnoreCase))
                {
                    url = url.Substring(0, url.Length - 6) + "0.jpg";
                }

                // Display progress info.
                txtProgress.Text = "Downloading page " + p + " of " + book.Pages.Length + "...";

                // Save to a temporary file first.
                string tmpfilename = filename + ".tmp";
                FileStream contentStream = new FileStream(tmpfilename, 
                    FileMode.Create, FileAccess.Write);

                // Download the page async.
                WebRequest request = WebRequest.Create(url);
                using (WebResponse response = await request.GetResponseAsync())
                {
                    using (Stream responseStream = response.GetResponseStream())
                    {
                        await responseStream.CopyToAsync(contentStream);
                    }
                }

                // Close the file and rename it.
                contentStream.Close();
                File.Move(tmpfilename, filename);
                //if (p > 3)
                //    break;
            }
            txtProgress.Text = "Done.";
#endif
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            SetWindowTheme(tvContents.Handle, "explorer", null);

            btnOpenBook_Click(btnOpenBook, null);
            //DoOpenBook(1321);
            //DoOpenBook(4481);
            //DoOpenBook(4569);

            //splitContainer1.Panel2.MouseWheel += Panel2_MouseWheel;
        }

        private void Panel2_MouseWheel(object sender, MouseEventArgs e)
        {
            MessageBox.Show("MouseWheel");
        }

        [DllImport("uxtheme", CharSet = CharSet.Unicode)]
        public extern static int SetWindowTheme(IntPtr hWnd, string textSubAppName, string textSubIdList);

        private void btnProperties_Click(object sender, EventArgs e)
        {
            PropertiesForm f = new PropertiesForm();
            f.SelectedObject = book;
            f.ShowDialog(this);
        }

        private static string FormatPageNumber(int pageNumber, int pageOffset)
        {
            int p = pageNumber + pageOffset;
            return (p > 0) ? p.ToString() : string.Format("C{0}", 1 + pageNumber);
        }

        private async void DisplayPage(int p)
        {
            currentPage = p;
            txtPageNumber.Text = FormatPageNumber(p, book.PageOffset);
            btnPrevPage.Enabled = (p > 0);
            btnNextPage.Enabled = (p < book.PageCount - 1);
#if false
            string url = book.Pages[p].ImageUrl;
            pictureBox1.ImageLocation = url;
#else
            // TODO: make this non-blocking.
            pictureBox1.Visible = false;
            Image newImage = await Task<Image>.Factory.StartNew(() =>
                {
                    return Image.FromStream(new MemoryStream(book.Pages[p].GetData()));
                });
            using (Image previousImage = pictureBox1.Image)
            {
                pictureBox1.Image = newImage;
            }
#endif
            AdjustPictureBox();
            splitContainer1.Panel2.AutoScroll = false;
            splitContainer1.Panel2.AutoScroll = true;
        }

        private void AdjustPictureBox()
        {
            if (pictureBox1.Image == null)
            {
                pictureBox1.Visible = false;
                return;
            }

            Size panelSize = splitContainer1.Panel2.ClientSize;
            Size imageSize = pictureBox1.Image.Size;

            // FIT-WIDTH:
            pictureBox1.Left = 0;
            pictureBox1.Top = 0;
            pictureBox1.Width = panelSize.Width;
            pictureBox1.Height = panelSize.Width * imageSize.Height / imageSize.Width;
            pictureBox1.Visible = true;
        }

        private void tvContents_AfterSelect(object sender, TreeViewEventArgs e)
        {
            TreeNode node = tvContents.SelectedNode;
            int p = (int)node.Tag;
            DisplayPage(p);
        }

        private int currentPage = -1;

        private void btnPrevPage_Click(object sender, EventArgs e)
        {
            if (currentPage > 0)
            {
                DisplayPage(currentPage - 1);
            }
        }

        private void btnNextPage_Click(object sender, EventArgs e)
        {
            if (currentPage < book.PageCount - 1)
            {
                DisplayPage(currentPage + 1);
            }
        }

        private async void btnDownloadBook_Click(object sender, EventArgs e)
        {
            progressBar1.Value = 0;
            progressBar1.Maximum = book.PageCount;
            progressBar1.Visible = true;

            //await SaveToXps();
            await SaveToFolder();

            progressBar1.Visible = false;
            txtPageNumber.Text = "Done.";
        }

        private static string GetNumberFormat(int max)
        {
            if (max < 0)
                throw new ArgumentOutOfRangeException("max");

            int k = (int)Math.Log10(Math.Max(1, max)) + 1;
            return "0000000000".Substring(0, k);
        }

#if false
        private async Task SaveToFolder()
        {
            if (folderBrowserDialog1.ShowDialog(this) != DialogResult.OK)
                return;

            string path = folderBrowserDialog1.SelectedPath;
            for (int p = 0; p < book.PageCount; p++)
            {
                string srcFileName = await book.DownloadPageAsync(p);
                string destFileName = Path.Combine(path, string.Format(
                    "p{0:" + GetNumberFormat(book.PageCount) + "}.jpg", p));
                progressBar1.Value = p + 1;

                if (File.Exists(destFileName))
                {
                    DialogResult r = MessageBox.Show(this, "Do you want to overwrite "
                        + destFileName + "?", "Save", MessageBoxButtons.YesNoCancel);
                    if (r == System.Windows.Forms.DialogResult.Cancel)
                        break;
                    if (r == System.Windows.Forms.DialogResult.No)
                        continue;
                }

                File.Copy(srcFileName, destFileName);
            }
        }
#else
        private async Task SaveToFolder()
        {
            if (folderBrowserDialog1.ShowDialog(this) != DialogResult.OK)
                return;

            string path = folderBrowserDialog1.SelectedPath;
            for (int p = 0; p < book.PageCount; p++)
            {
                byte[] pageData = await Task<byte[]>.Factory.StartNew(
                    () => book.Pages[p].GetData());
                string destFileName = Path.Combine(path, string.Format(
                    "p{0:" + GetNumberFormat(book.PageCount) + "}.jpg", p));
                progressBar1.Value = p + 1;

                if (File.Exists(destFileName))
                {
                    DialogResult r = MessageBox.Show(this, "Do you want to overwrite "
                        + destFileName + "?", "Save", MessageBoxButtons.YesNoCancel);
                    if (r == System.Windows.Forms.DialogResult.Cancel)
                        break;
                    if (r == System.Windows.Forms.DialogResult.No)
                        continue;
                }

                using (FileStream output = File.OpenWrite(destFileName))
                {
                    output.Write(pageData, 0, pageData.Length);
                }
            }
        }
#endif

#if false
        private async Task SaveToXps()
        {
            XpsDocument doc = new XpsDocument("test.xps", FileAccess.ReadWrite);
            XpsPackagingPolicy policy = new XpsPackagingPolicy(doc);
            var seqWriter = doc.AddFixedDocumentSequence();
            //doc.FixedDocumentSequenceReader.
            var docWriter = seqWriter.AddFixedDocument();

            Uri[] pageUri = new Uri[book.PageCount];

            for (int p = 0; p < book.PageCount; p++)
            {
                string imagePath = await book.DownloadPageAsync(p);
                progressBar1.Value = p + 1;

                var pageWriter = docWriter.AddFixedPage();
                XpsImage image = pageWriter.AddImage(XpsImageType.JpegImageType);
                using (Stream destStream = image.GetStream())
                using (Stream srcStream = File.OpenRead(imagePath))
                {
                    await srcStream.CopyToAsync(destStream);
                }
                //MessageBox.Show("Last URI: " + image.Uri.ToString());
                int w = (int)(book.PageSize.Width * 100);  // TBD
                int h = (int)(book.PageSize.Height * 100); // TBD

                var xml = pageWriter.XmlWriter;
                xml.WriteStartElement("FixedPage", "http://schemas.microsoft.com/xps/2005/06");
                xml.WriteAttributeString("Width", w.ToString());
                xml.WriteAttributeString("Height", h.ToString());
                xml.WriteAttributeString("xml:lang", "en-US");
                xml.WriteAttributeString("Name", "Page_" + p);
                xml.WriteStartElement("Canvas");

                xml.WriteStartElement("Path");
                //xml.WriteAttributeString("SnapsToDevicePixels", "true");
                // Note: in XPS viewer, there always appears to be two vertical
                // white lines on the left and right side of each page. I don't
                // know how to get rid of it.
                xml.WriteAttributeString("Stroke", "#000000");
#if false
                xml.WriteAttributeString("StrokeThickness", "100");
#else
                xml.WriteAttributeString("StrokeThickness", "0");
#endif
#if false
                xml.WriteStartElement("Path.Data");
                xml.WriteStartElement("PathGeometry");
                xml.WriteStartElement("PathFigure");
                xml.WriteAttributeString("IsClosed", "true");
                xml.WriteAttributeString("StartPoint", "0,0");
                xml.WriteStartElement("PolyLineSegment");
                xml.WriteAttributeString("Points", string.Format(
                    "{0},{1} {2},{3} {4},{5}",
                    w, 0, w, h, 0, h));
                xml.WriteEndElement(); // PolyLineSegment
                xml.WriteEndElement(); // PathFigure
                xml.WriteEndElement(); // PathGeometry
                xml.WriteEndElement(); // Path.Data
#else
                xml.WriteAttributeString("Data",
                    String.Format("M 0,0 H {0} V {1} H 0 Z", w, h));
#endif
                xml.WriteStartElement("Path.Fill");
                xml.WriteStartElement("ImageBrush");
                xml.WriteAttributeString("ImageSource", image.Uri.ToString());
                xml.WriteAttributeString("Viewbox", string.Format("0,0,{0},{1}",
                    book.ImageWidth, Math.Round((double)book.ImageWidth / w * h)));
                xml.WriteAttributeString("ViewboxUnits", "Absolute");
                xml.WriteAttributeString("Viewport", string.Format("0,0,{0},{1}",
                    w, h));
                xml.WriteAttributeString("ViewportUnits", "Absolute");
                xml.WriteEndElement(); // ImageBrush
                xml.WriteEndElement(); // Path.Fill
                xml.WriteEndElement(); // Path

                xml.WriteEndElement(); // Canvas
                xml.WriteEndElement(); // FixedPage

                pageWriter.LinkTargetStream.Add("Page_" + p);
                pageWriter.Commit();
                pageUri[p] = pageWriter.Uri;
                // break;
            }

            var docStruct = docWriter.AddDocumentStructure();
            using (var tocWriter = new System.Xml.XmlTextWriter(
                docStruct.GetStream(), Encoding.UTF8))
            {
                tocWriter.WriteStartElement("DocumentStructure",
                    "http://schemas.microsoft.com/xps/2005/06/documentstructure");
                tocWriter.WriteStartElement("DocumentStructure.Outline");
                tocWriter.WriteStartElement("DocumentOutline");

                foreach (Section a in book.Sections)
                {
                    int p = a.PageNumber;
                    if (p >= 0 && p < pageUri.Length)
                    {
                        tocWriter.WriteStartElement("OutlineEntry");
                        //tocWriter.WriteAttributeString("OutlineLevel", "1");
                        tocWriter.WriteAttributeString("Description", a.Title);
                        tocWriter.WriteAttributeString("OutlineTarget",
                            docWriter.Uri.ToString() + "#Page_" + p);
                        tocWriter.WriteEndElement(); // OutlineEntry
                    }
                }

                tocWriter.WriteEndElement(); // DocumentOutline
                tocWriter.WriteEndElement(); // DocumentStructure.Outline
                tocWriter.WriteEndElement(); // DocumentStructure
            }
            docStruct.Commit();

            docWriter.Commit();
            seqWriter.Commit();

            var props = doc.CoreDocumentProperties;
            props.Title = book.Title + " - " + book.Issue;
            doc.Close();

#if false
                // Save to a temporary file first.
                string tmpfilename = filename + ".tmp";
                FileStream contentStream = new FileStream(tmpfilename,
                    FileMode.Create, FileAccess.Write);

                // Close the file and rename it.
                contentStream.Close();
                File.Move(tmpfilename, filename);
#endif
        }
#endif

        private async void btnTest_Click(object sender, EventArgs e)
        {
            // Open data file.
            string logfile = "scan.log";
            FileStream stream = new FileStream(logfile, FileMode.OpenOrCreate, FileAccess.ReadWrite);

            // See our progress.
            int last = 0;
            using (StreamReader reader = new StreamReader(stream, Encoding.UTF8, false, 1024, true))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    string[] elements = line.Split(new char[] { '\t' });
                    last = Int32.Parse(elements[0]);
                }
            }

            // Try to get meta data of all books! Do not cache.
            //Book.CacheManager.CacheDirectory = null;
            using (StreamWriter writer = new StreamWriter(stream))
            {
                for (int id = last + 1; id <= last + 1000000; id++)
                {
                    btnTest.Text = id.ToString();
                    string line = null;
                    try
                    {
                        Book b = await Task<Book>.Factory.StartNew(() => Book.Load(id));
                        line = string.Format("{0}\tOK\t{1}\t{2}\t{3}",
                            id, b.Title, b.Issue, b.PageCount);
                        this.Text = line;
                    }
                    catch (Exception ex)
                    {
                        line = string.Format("{0}\tERROR\t{1}", id, ex.Message);
                    }
                    writer.WriteLine(line);
                    writer.Flush();
                }
            }
            MessageBox.Show("Scanned 1000 items.");
        }

        private void btnOpenBook_Click(object sender, EventArgs e)
        {
            BookListForm f = new BookListForm();
            if (f.ShowDialog(this) != DialogResult.OK)
                return;

            int id = f.SelectedId;
            DoOpenBook(id);
        }

        private void pictureBox1_Click(object sender, EventArgs e)
        {

        }

        private void splitContainer1_Panel2_ClientSizeChanged(object sender, EventArgs e)
        {
            AdjustPictureBox();
            splitContainer1.Panel2.AutoScroll = true;
            //AdjustFormScrollbars
        }
    }
}
